import type { Token } from './type'
import { TokenTypeEnum } from './type'

/**
 * 文本流分段器 - 用于将连续文本按语言规则智能分割成有意义的段落和片段
 * 支持中文、英文混合文本处理，正确识别单词、标点符号和数字，避免小数点等特殊符号导致的错误分段
 */
class TextStreamSplitter {
  // 中文字符
  private readonly CHINESE_CHAR_REGEX = /[\u4E00-\u9FA5]/
  // 中文标点符号
  private readonly CHINESE_PUNCTUATION_REGEX = /[。？！，；：“”‘’（）—…、·]/
  // // 中文段落结束符号（句末标点）
  private readonly CHINESE_PARAGRAPH_END_REGEX = /[。？！；]/
  // 英文单词起始字符
  private readonly ENGLISH_WORD_START_REGEX = /[a-z]/i
  // 英文单词延续字符（含缩写和连字符）
  private readonly ENGLISH_WORD_CONTINUE_REGEX = /[a-z'’-]/i
  // 英文标点符号
  private readonly ENGLISH_PUNCTUATION_REGEX = /[.,!?;:'"()-]/
  // 英文段落结束符号（句末标点）
  private readonly ENGLISH_PARAGRAPH_END_REGEX = /[.!?;]/
  //  空白字符（统一转为单个空格）
  private readonly SPACE_CHAR_REGEX = /\s/
  // 最小段落长度
  private readonly MIN_PARAGRAPH_LENGTH = 10

  // 待处理的剩余文本标记
  private pendingTokens: Token[] = []

  /**
   * 将输入文本转换为标记流。
   * @param text 原始文本内容。
   * @returns 标记化后的文本标记数组。
   */
  private tokenizeText(text: string): Token[] {
    const tokens: Token[] = []
    let currentIndex = 0

    while (currentIndex < text.length) {
      const char = text[currentIndex]

      // 识别中文字符
      if (this.CHINESE_CHAR_REGEX.test(char)) {
        tokens.push({
          type: TokenTypeEnum.CHINESE_CHAR,
          value: char,
        })
      }
      // 识别英文单词
      else if (this.ENGLISH_WORD_START_REGEX.test(char)) {
        const startIndex = currentIndex
        let endIndex = currentIndex + 1

        // 提取完整英文单词
        while (endIndex < text.length && this.ENGLISH_WORD_CONTINUE_REGEX.test(text[endIndex])) {
          endIndex++
        }

        const word = text.substring(startIndex, endIndex)
        tokens.push({
          type: TokenTypeEnum.ENGLISH_WORD,
          value: word,
        })

        // 跳过已处理的字符
        currentIndex += word.length - 1
      }
      // 识别中文标点符号
      else if (this.CHINESE_PUNCTUATION_REGEX.test(char)) {
        tokens.push({
          type: TokenTypeEnum.CHINESE_PUNCTUATION,
          value: char,
        })
      }
      // 识别英文标点符号
      else if (this.ENGLISH_PUNCTUATION_REGEX.test(char)) {
        tokens.push({
          type: TokenTypeEnum.ENGLISH_PUNCTUATION,
          value: char,
        })
      }
      // 识别空白字符
      else if (this.SPACE_CHAR_REGEX.test(char)) {
        tokens.push({
          type: TokenTypeEnum.SPACE_CHAR,
          value: char,
        })
      }
      // 未知字符类型
      else {
        tokens.push({
          type: TokenTypeEnum.UNKNOWN_CHAR,
          value: char,
        })
      }

      currentIndex++
    }

    return tokens
  }

  /**
   * 文本净化预处理 - 去除无效字符和格式。
   * @param text 原始文本。
   * @returns 净化后的文本。
   */
  private sanitizeText(text: string): string {
    return text
      .replace(/\s+/g, ' ') // 将连续空白字符转为单个空格
      .replace(/(\d+)\s+/g, '$1') // 去除数字后的空白
      .replace(/\*+/g, '') // 移除星号
      .replace(/[\u{1F000}-\u{1FAFF}]/gu, '') // 移除表情符号
  }

  /**
   * 段落输出之前的整理逻辑。
   * @param paragraphs 段落数组。
   * @returns 整理后的段落数组。
   */
  private sanitizeParagraphs(paragraphs: string[]): string[] {
    return paragraphs
      .map(paragraph => paragraph.trim())
      .filter(paragraph => paragraph.length > 0)
  }

  /**
   * 判断标记是否为段落结束符号。
   * @param token 文本标记。
   * @returns 是否为段落结束符号。
   */
  private isParagraphEndToken(token: Token): boolean {
    return (
      this.CHINESE_PARAGRAPH_END_REGEX.test(token.value)
      || this.ENGLISH_PARAGRAPH_END_REGEX.test(token.value)
    )
  }

  /**
   * 将标记数组转换回文本。
   * @param tokens 文本标记数组。
   * @returns 组合后的文本。
   */
  private tokensToString(tokens: Token[]): string {
    return tokens.map(token => token.value).join('')
  }

  /**
   * 将标记流分割成多个段落。
   * @param tokens 文本标记数组。
   * @returns 分段后的标记数组集合。
   */
  private splitToParagraphs(tokens: Token[]): Token[][] {
    const paragraphs: Token[][] = []
    let currentParagraph: Token[] = []

    for (const token of tokens) {
      currentParagraph.push(token)

      // 满足最小长度且遇到段落结束符号时分割段落
      if (
        currentParagraph.length >= this.MIN_PARAGRAPH_LENGTH
        && this.isParagraphEndToken(token)
      ) {
        paragraphs.push(currentParagraph)
        currentParagraph = []
      }
    }

    // 添加剩余的文本
    if (currentParagraph.length > 0) {
      paragraphs.push(currentParagraph)
    }

    return paragraphs
  }

  /**
   * 合并因小数点导致的错误分段。
   * @param paragraphs 分段后的标记数组集合。
   * @returns 合并后的标记数组集合。
   */
  private mergeDecimalParagraphs(paragraphs: Token[][]): Token[][] {
    return paragraphs.reduce((mergedParagraphs: Token[][], currentParagraph: Token[]) => {
      if (mergedParagraphs.length === 0) {
        mergedParagraphs.push(currentParagraph)
      }
      else {
        const lastParagraph = mergedParagraphs[mergedParagraphs.length - 1]
        const lastParagraphText = this.tokensToString(lastParagraph)
        const currentParagraphText = this.tokensToString(currentParagraph)

        // 如果上一段以数字加小数点结尾，且当前段以数字开头，则合并两段
        if (/\d+\.$/.test(lastParagraphText) && /^\d+/.test(currentParagraphText)) {
          mergedParagraphs[mergedParagraphs.length - 1] = [...lastParagraph, ...currentParagraph]
        }
        else {
          mergedParagraphs.push(currentParagraph)
        }
      }

      return mergedParagraphs
    }, [])
  }

  /**
   * 处理输入文本，返回分段后的文本数组。
   * @param text 输入文本内容。
   * @param includeRemaining 是否包含未完成的段落。
   * @returns 分段后的文本数组。
   */
  public processText(text: string, includeRemaining: boolean = false): string[] {
    // 净化并标记化文本
    const tokens = this.tokenizeText(this.sanitizeText(text))

    // 合并历史遗留的未完成段落
    const combinedTokens = [...this.pendingTokens, ...tokens]

    // 分段并处理小数点问题
    const paragraphs = this.mergeDecimalParagraphs(this.splitToParagraphs(combinedTokens))

    // 处理未完成的段落
    if (paragraphs.length > 0) {
      const lastParagraph = paragraphs[paragraphs.length - 1]
      const lastToken = lastParagraph[lastParagraph.length - 1]

      if (includeRemaining) {
        // 返回所有内容，清空待处理标记
        this.pendingTokens = []
      }
      else {
        // 检查最后一段是否完整
        if (this.isParagraphEndToken(lastToken)) {
          // 完整段落但以数字加小数点结尾，视为不完整
          if (/\d+\.$/.test(this.tokensToString(lastParagraph))) {
            this.pendingTokens = paragraphs.pop()!
          }
          else {
            this.pendingTokens = []
          }
        }
        else {
          // 不完整段落，保留待处理
          this.pendingTokens = paragraphs.pop()!
        }
      }
    }

    // 返回分段后的文本
    return this.sanitizeParagraphs(paragraphs.map(paragraph => this.tokensToString(paragraph)))
  }
}

export default TextStreamSplitter
